/* 
  webpack.config.js 通常放在项目的根目录中，是一个标准的Commonjs规范的模块。
  ctrl k +  ctrl 0  全部折叠
  
  1. Loader 机制是 webpack 的核心 --------> Loader专注实现资源模块加载
    通过使用不同的loader,  webpack有能力调用外部的脚本或工具，实现对不同格式的文件的处理
    比如说分析转换scss为css,  或者把下一代的JS文件（ES6，ES7)转换为现代浏览器兼容的JS文件
    对React的开发而言,  合适的Loaders可以把React的中用到的JSX文件转换为JS文件。
    ----- Loaders需要单独安装并且需要在webpack.config.js中的modules关键字下进行配置
    
  2. plugin 插件机制是 webpack 的另外一个核心核心，目的增强webpack自动化能力
    --------> 解决其他自动化工作: 比如自动清除dist目录 / 拷贝无需打包的资源文件到输出目录 / 压缩输出代码
    webpack 常用插件:
    1. clean-webpack-plugin
    2. html-webpack-plugin
    3. copy-webpack-plugin
*/
const webpack = require('webpack'); // 这样就可以使用内置插件了
const path = require('path') // 引入node的path模块, 绝对路径的获取 通过 node中的 path 去转换

/*
  网络补充：
  1. 代码压缩-------minimize: true 
  var uglifyPlugin = new webpack.optimize.UglifyJsPlugin({minimize: true});

  2. 把公共模块提取出来, 并取名为'common'(名字自起), webpack之后再out文件夹下生成common.js, 测试时记得引入提取出来的公共模块js文件-----
  var CommonsChunkPlugin = new webpack.optimize.CommonsChunkPlugin('common');

  3. 将css独立引入变成link标签形式, 
  安装：npm install extract-text-webpack-plugin --save-dev, 
  导入：const ExtractTextPlugin = require("extract-text-webpack-plugin");
  同时下面的rules也必须更改------
  test: /\.css$/,
  loaders: ExtractTextPlugin.extract({
    use: ['css-loader'],
  }),
  plugins: [
    new ExtractTextPlugin({
      filename:'[name]_[contenthash:8].css',
    }),
  ],

  4. 引入jquery------
  var providePlugin = new webpack.ProvidePlugin({$: 'jquery', jQuery: 'jquery', 'window.jQuery': 'jquery'});

  plugins: [uglifyPlugin, CommonsChunkPlugin, new ExtractTextPlugin('[name].css'), providePlugin] //插件集合
*/

const { CleanWebpackPlugin } = require('clean-webpack-plugin') // yarn add clean-webpack-plugin --dev
const HtmlWebpackPlugin = require('html-webpack-plugin') // yarn add html-webpack-plugin --dev
const CopyWebpackPlugin = require('copy-webpack-plugin') //  yarn add copy-webpack-plugin --dev

module.exports = {
  /*
    mode 属性有三种取值:
    1. production 生产模式，Webpack 会自动优化打包结果
    2. developmen 开发模式，Webpack 会自动优化打包速度，添加一些调试过程中的辅助
    3. none 模式， Webpack 就是运行最原始的打包，不做任何额外处理
  */
  mode: 'development',

  /*
    入口文件 entry,可以是 字符串 or 数组 or 是对象
    字符串时，用来定义入口文件,默认src/index.js做为打包入口，---> dist/main.js
    数组时，同样包含入口js文件，另一个参数可以是用来配置webpack提供的一个静态资源服务 webpack-dev-server。
    对象时，可将不同文件构建成不同的js文件，按需使用，比如在我的hello页面中只要\引入hello.js即可：
    entry: {
      hello: './js/hello.js',   [name].js用于定义不同文件构建后的名字。
      form: './js/form.js'
    }
  */
  entry: './src/main.js',

  /**
   定义构建后的文件的输出 output:{}
    filename: // 输出文件名,
    path: // 输出 的 绝对路径：
      输出目录，必须是绝对路径，引入路径依赖包path, 此处得到output的完整绝对路径
      path.resolve(__dirname,'dist') 将相对路径转成绝对路径
      __dirname指的是当前文件【webpack.config.js】的上一级路径

    publicPath: 'dist/'
    publicPath：相对于HTML页面解析的输出目录的url
    静态资源打包路径 , 默认值是"", 表示根目录，要放在dist目录下，/不能少,
      html文件所引用的目录也要更改:
        <link rel="stylesheet" href="...">
        <a href="...">
        <script src="...">
      自动生成的HTML文件里 引用的js 文件  要取消publicPath: 'dist/'，不然文件路径为dist/xx.js
  */
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'output'),
  },

  // Tree Shaking  去除代码中未引用的代码
  optimization: {
    usedExports: true,   //  标记多余未用到的导出内容  需要 移除掉,模块只导出被使用的成员
    minimize: true,  // 移除, 开启代码压缩功能，压缩掉没有用到的代码
    // concatenateModules  尽可能合并每一个模块到一个函数中,继续优化输出，该属性又被称为 Scope Hoisting 作用域提升
    concatenateModules: true, // 尽可能将所有模块合并输出到一个函数中, 既提升了运行效率，又减少的代码的体积
    sideEffects: true, // 没有副作用 就会被移除掉
    minimizer: [
      new TerserWebpackPlugin(),
      new OptimizeCssAssetsWebpackPlugin()
    ],
    // 自动提取所有公共模块到单独 bundle
    splitChunks: {
      chunks: 'all'
    }
  },

  // 代码映射，增强调试，以构建速度为代价
  devtool: "source-map",

  /*
    webpack-dev-server是一个轻量级的服务器， 开发服务器, 模块热替换
    模块热替换相对于默认的刷新机制能提供更快的响应速度和更好的开发体验，默认是关闭的
    安装：npm install webpack-dev-server -D
    命令: 打包时输入这一行可以直接找到文件如果出错时的位置
    'webpace-dev-server --devtool eval-source-map --progess --colors'

    也可写入package.json 的 script 里, 例如：
    "build": "webpack src/main.js dist/bundle.js"
    "watch": "npm run build -- --watch"
    "build": "npx webpack --config webpack.config.js"
    "dev": "webpack-dev-server --open --config webpack.config.js"

    --config是指向webpac-dev-server读取的配置文件路径，这里直接读取我们在上一步创建的webpack.config.js文件
    --open会在执行命令时自动在浏览器中打开页面，默认地址是127.0.0.1:8080

    • 监昕文件的变化并自动刷新网页，做到实时预览：
    • 支持生成 Source Map ,只需在启动时带上一 devtool source-map参数。映射代码，以方便在源代码上断点调试。
    • devServer 会启动一个 HTTP 服务器用于服务网页请求
      同时会帮助启动Webpack，并接收 Webpack 发出的文件更变信号
      通过 WebSocket 协议自动刷新网页做到实时预览。

    热更新默认 hot  的方式，如果热更新失败，会自动回退去使用页面刷新
    ( 处理HMR 的代码报错会导致自动刷新，推荐用 hotonly解决,不会使用自动刷新)
    devServer: { hotOnly : true }
  */
  devServer: {
    port: 3000, // 默认3000 
    publicPath: "./",
    proxy: { // 代理url
      '/api': 'http://localhost:3000'
    },
    contentBase: path.join(__dirname, 'public'), // boolean | string | array, static file location
    compress: true, // 支持gzip压缩
    historyApiFallback: true, // html在404，对象为多个路径
    hot: true, // 热模块替换。取决于HotModuleReplacementPlugin
    https: false,
    noInfo: true,
    // ...
  },

  /*
   module: {} 项目需要用到的不同类型 资源模块加载 ，内部loader只能处理js文件，其他文件需要其他loader
    里面的rules 引入加载器规则
      rules:[{ 有test 匹配后缀的文件, 有use }, {}, {}, {}] 
      rules数组 用于规定在不同模块被创建时如何处理模块的规则数组

    npm install babel-loader babel babel-core css-loader style-loader  url-loader file-loader less-loader less  --save-dev
  */
  module: {
    rules: [
      /*   one 脚本文件
        js 文件
        常用加载器模块 分类：编译转换类，转为js代码
        文件操作类，加载的资源拷贝到输出目录，同时导出它的输出路径，如file-loader
        代码检查类，统一代码风格
        因为模块打包需要所以处理import 和 export ，并不处理ES6
        指定并取代默认的加载器, 打包中处理新特性
        安装：
          1. yarn  add  babel-loader @babel/core @babel/preset-env --dev

        Babel其实是一个编译JavaScript的平台，它可以编译代码实现：
          1. 使用最新的JavaScript代码（ES6，ES7...），而不用管新标准是否被当前使用的浏览器完全支持
          2. 能使用基于JavaScript进行了拓展的语言，比如React的JSX
        Babel其实是几个模块化的包，其核心功能位于称为babel-core的npm包中
        webpack可以把其不同的包整合在一起使用，对于每个你需要的功能或拓展，都需要安装单独的包
        ( 用得最多的是解析Es6的 babel-env-preset包和解析JSX的 babel-preset-react包 )

        Babel其实可以完全在 webpack.config.js 中进行配置，但是考虑到babel具有非常多的配置选项
        在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂
        因此一些开发者支持把babel的配置选项放在一个单独的名为 ".babelrc" 的配置文件中。
      */
      {
        test: /\.js$/,
        /* 
          exclude 规则排除的范围 例如：exclude:[ path.resolve(__dirname, "app/demo-files")],
          include 规则适用的范围 例如：include: [ path.resolve(__dirname, "app")]
        */
        exclude: /node_modules/,
        use: 'babel-loader',
        options: {
          // 需要的是presets内部插件集合，才能真的转换，加载器可以用来编译转换代码
          presets: ['@babel/preset-env']
        }
      },

      //  使用eslint 结合webpack 使用
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'eslint-loader',
        enforce: 'pre' // 先校验后转化浏览器可识别的 js
      },

      /**  two 样式文件
        css 文件：
        use:[多个loader] 使用的模块有，从后往前执行   ----or----     use:'单个loader'
        解析css, 并把css添加到html的style标签里: use: ['style-loader', 'css-loader']
        解析css, 并把css变成文件通过link标签引入： use: ExtractTextPlugin.extract({fallback: 'style-loader',use: 'css-loader'})
        安装：
          1. yarn add style-loader--dev, 'style-loader'将css样式文件 转化成style标签形式追加到在html页面上
          2. yarn add  css-loader--dev,  'css-loader'通过这个将css文件转换成js文件，才可以正常打包

        css-loader使你能够使用类似@import 和 url(...)的方法实现 require()的功能
      */
      // {
      //   test: /.css$/,
      //   use: ['style-loader', 'css-loader']
      // },
      {
        test: /\.css$/,
        use: [
          // 'style-loader', // 将样式通过 style 标签注入
          MiniCssExtractPlugin.loader, // 单独抽取出来的css文件，通过 link 的方式去引入
          'css-loader'
        ]
      },

      /*
      css代码从打包结果中提取出来的插件,  实现  CSS 文件的 按需加载
      ### yarn add mini-css-extract-plugin --dev
      ### const MiniCssExtractPlugin = require('mini-css-extract-plugin')

      ### yarn  add  terser-webpack-plugin --dev     内部的js 压缩器
      ### const   TerserWebpackPlugin = require('terser-webpack-plugin')
      ### yarn  add  optimize-css-assets-webpack-plugin  --dev  css文件压缩器
      ### const  OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

      补充：scss样式文件的模块化
      xxx.scss文件
       定义可复用的样式
       @mixin center {
        position: absolute;
        left: 50%;
        top : 50%;
        transform : translate(-50%,-50%);
      }

      yyy.scss文件
       引用可复用的样式片段
       @import "xxx"; // 导入样式文件
       #box{
        @include center ;
       }
      */

      /**
        less 文件：
        use: ['style-loader', 'css-loader', 'less-loader'] 解析less, 把less解析成浏览器可以识别的css语言
        安装：
          1. yarn add style-loader--dev, 'style-loader'将css样式文件 转化成style标签形式追加到在html页面上
          2. yarn add  css-loader--dev,  'css-loader'通过这个将css文件转换成js文件，才可以正常打包
      */
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },

      /*   three 图片、字体等资源文件
        匹配图片后缀：
        test:/\.(jpg|png|gif|jpeg|svg|bmp)$/
        use: ['url-loader?limit=8192&name=./[name].[ext]']},  解析图片
        
        小文件使用 Data URLs,减少请求次数
        大文件单独提取存放，提高加载速度
        option：{}添加配置选项, 需要将 use 改为对象
        安装：
          1. yarn add url-loader --dev, 不请求服务器，以代码形式展现文件,
          base64编码后的结果，一个字符串去表示这个内容, 可替换 file-loader (文件操作类，加载的资源拷贝到输出目录)
          如此dist 下就不会有图片文件了，在打包后的js文件中，导入的该字符串可以直接在网址中打开
          2. yarn add  file-loader --dev, 图片依赖file-loader

        匹配字体文件后缀：  
        test: /\.(woff|woff2|eot|ttf)$/
        use: 'url-loader'
      */
      {
        test: /\.png$/,
        // use: 'file-loader' 
        // use: 'url-loader',
        use: {
          loader: 'url-loader',
          options: {
            // limit 限定条件
            limit: 10 * 1024 // 10 KB ，单位是字节B ，超过的部分交给file-loader处理，所以 url-loader file-loader 都不可少
          }
        }
      },

      /*    four   html 文件
       1.  webpack 兼容多种模块化标准，最好不好混用，不利于维护
       ------------遵循 ES Modules 标准的 import 声明
       ------------遵循 CommonJS 标准的 require 函数  module.exports = xxx; const xxx = require('路径')
       ------------遵循 AMD 标准的 define 函数 和 require 函数，与CommonJS最大的不同是采用了异步的方式去加载依赖的模块。

       2. Loader 加载的非 JavaScript 也会触发资源加载，
        比如 @import执行，和 url 函数  @import url(xxx.css)/ background-image:url(xxx.png)
        比如 HTML 代码中图片标签的 src 属性,  超链接的 href
        var oImg = new Image();
        oImg.src = require('../img/1.gif');  // 当成模块引入图片
        document.body.appendChild(oImg);

       3. 想webpack认识html 打包
        安装：yarn add html-loader -dev
      */
      {
        test: /.html$/,
        use: {
          loader: 'html-loader',
          options: { // 需要处理的属性
            attrs: ['img:src', 'a:href']
          }
        }
      },


      /**
        webpack 开发一个loader，比如 用到markdown-loader加载器，
        实现在代码中可以直接导入 markdown 文件，转换成HTML后呈现
        打包后的内容必须是个标准的js代码，如果是字符串需要转换, 需要 html-loader，
        转换后的HTML是字符串，所以可以起个别名 import  别名 from 'xxx'
        安装：yarn add marked --dev  (markdown 解析模块 )
      */
      {
        test: /.md$/,
        use: [// 依旧是从下往上执行，像是一个管道，同个资源可以依次使用多个loader
          'html-loader',
          './markdown-loader'
        ]
      }
    ]
  }, // module：{}  结束

  /*
    webpack在构建包的时候会按目录的进行文件的查找
    resolve属性中的extensions数组中用于配置程序可以自行补全哪些文件后缀：
    然后我们想要加载一个js文件时，只要require('common')就可以加载 common.js文件了。
  */
  resolve: {
    extensions: ['', '.js', '.json']
  },

  /**
   * 当我们想在项目中require一些其他的类库或者API，而又不想让这些类库的源码被构建到运行时文件中，这在实际开发中很有必要。此时我们就可以通过配置 externals:{} 参数来解决这个问题
   * 这样我们就可以放心的在项目中使用这些API了, 安装：npm install jquery --save-dev 导入：var jQuery = require('jquery');
   */
  externals: {
    "jquery": "jQuery"
  },

  /**  
   * plugins: []  webpack 插件列表
   * webpack 常用插件：
   * * 1. clean-webpack-plugin: 自动清理输出目录的插件
   *    安装插件：yarn add clean-webpack-plugin --dev
   *    然后导入：const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 解构的方式读取
   *    使用插件要使用 module属性同级的 plugins:[] 配置插件
   * 
   * * 2. html-webpack-plugin
   *    通过webpack 自动生成 html文件，避免硬编码和手动修改路径, 默认 src目录下的index.html为模板文件
   *    每个页面都要 重新定义 HtmlWebpackPlugin 实例对象，传参为对象
   *    安装插件：yarn add html-webpack-plugin --dev
   *    然后导入：const HtmlWebpackPlugin = require('html-webpack-plugin')，默认导出插件类型，无需结构内部成员
   * 
   * * 3. copy-webpack-plugin
  *     比如静态文件  public里的，打包是一并把他们放进输出目录， 使用 copy-webpack-plugin
  *     yarn add copy-webpack-plugin --dev,
  *     然后倒入插件类型，const CopyWebpackPlugin = require('copy-webpack-plugin')
  *     要求传入数组，指定需要拷贝的文件路径，可以是通配符，目录或者是文件的相对路径
  * 
  *   plugins 属性是一个数组，里面的每一项都是插件的一个实例，在实例化一个组件时可以通过构造函数传入这个组件支持的配置属性。
      例如：
      ExtractTextPlugin插件的作用是提取出JavaScript代码里的css到一个单独的文件中。
        对此我们可以通过插件的filename属性，告诉插件，输出的css文件名称 是通过[name][contenthash:8].css字符串模板生成的，
        里面的[ name]代表文件的名称，
        [contenthash:8]代表根据文件内容算出的8位Hash值，还有很多配置选项可以在
        ExtractTextPlugin ( https://github.com/webpack-contrib/extract-text-webpack-plugin ）的主页上查到。
  */
  plugins: [
    new webpack.ProgressPlugin(), // 放入一个实例
    new CleanWebpackPlugin(), // 通过webpack 自动生成html文件，避免硬编码和手动修改路径

    /* 
      对于自动生成的html文件，传入参数，修改文件title啊之类的，先定义的html文件可以用lodash的模板语法去输出
      <%= htmlWebpackPlugin.options.属性 %> 属性比如下面的title
      每次打包都会将 index.html 模板文件复制到dist 输出文件夹中，并且自动为其引入 输出.js 文件
    */
    new HtmlWebpackPlugin({ // 
      title: 'Webpack Plugin Sample',
      meta: {
        viewport: 'width=device-width'
      },
      template: './src/index.html' // 指定我们的模板文件路径
    }),

    new HtmlWebpackPlugin({
      filename: 'about.html'
    }),

    new CopyWebpackPlugin([
      // 'public/**'
      'public'
    ]),

    new ExtractTextPlugin({
      // 从.js文件中提取出来的.css文件的名称
      filename: '[name]_[contenthash:8].css',
    }),
    
    new MiniCssExtractPlugin({// 通过 link 的方式去引入
      filename: '[name]-[contenthash:8].bundle.css'
    }),
    /*
     DefinePlugin 是 webpack内置插件：为代码注入全局成员，需要 const webpack = require('webpack'); 
      要求是：( 构造函数接收一个对象) 值是一个 js 代码片段，所以是单引号里还有双引号，字面量语句
      如果value是一个值的话，JSON.stringify(value) 将值转化为一个表示的 js 代码片段

      默认开启definePlugin 插件并注入 process.env.NODE_ENV 这个常量，用来判断环境
    */
    new webpack.DefinePlugin({
      API_BASE_URL: '"https://xxxxxx.com"',
    }),

  ]
}



/**
  Webpack是属于Node的程序，Node环境下的环境变量，Webpack可以配置，可以灵活读取。
  但是index.js里面是属于Webpack要构建的产物，如果里面也想读取环境变量。
  可以通过这个DefinePlugin定义一下，index.js里面就可以读到了。
 
 new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: '"production"'
    }
  }),
 
*/

/**
 *  webpack安装, 首先安装Node.js,
    推荐安装到本项目，原因是可防止不同的项目因依赖不同版本的webpack而导致冲突
    安装： 项目中 cnpm install webpack --save-dev (或者  -D )  / 全局npm install webpack -g
    运行：webpack-dev-server --progress --colors

    此外还需安装webpack-cli： 
    npm install webpack-cli --save-dev

 * 开发环境,当项目逐渐变大，编译时间会变长，可以通过参数让编译的输出内容带有进度和颜色
 * 编译输出内容带进度和颜色: webpack --progress --colors
 * 启动监听模式：webpack --progress --colors --watch
 * 另外可以使用 webpack-dev-server ,能通过 localhost:8080 启动一个 express 静态资源 web 服务器，
    并以监听模式自动运行 webpack,
    并且通过一个 socket.io 服务实时监听它们的变化并自动刷新页面。
    如果没有这个，那每次修改代码后，都要重新打包才能看到效果，
    它可启动一个简易的http 服务器，代码更新，实时打包更新的内容，效率比较高
    打包内容都是放在呢村里面的，所以访问速度也会更快
    还可以给命令传参： webpack-dev-server --port 3000 --contentBase ./src --hot
 */


 /**
  * webpack，gulp/grunt，npm， 有什么区别?
  ----- webpack 是模块打包器（module bundler），把所有的模块打包成一个或少量文件，使你只需加载少量文件即可运行整个应用，而无需像之前那样加载大量的图片，css文件，js文件，字体文件等等。
  ----- gulp／grunt 是自动化构建工具，或者叫任务运行器（task runner），是把你所有重复的手动操作让代码来做，
  例如压缩JS代码、CSS代码，代码检查、代码编译等等，自动化构建工具并不能把所有模块打包到一起，也不能构建不同模块之间的依赖图。
  
  两者来比较的话:
    * gulp/grunt 无法做模块打包的事
    * webpack 虽然有 loader 和 plugin可以做一部分 gulp／grunt 能做的事，但是终究 webpack 的插件还是不如 gulp／grunt 的插件丰富，能做的事比较有限。
  于是两者结合着用，将 webpack 放到 gulp／grunt 中用,
  更好的方法是用 npm scripts 取代 gulp／grunt, 
    npm 是 node 的包管理器 (node package manager)，用于管理 node 的第三方软件包，
    npm 对于任务命令的良好支持让你最终省却了编写任务代码的必要，取而代之的是，几个命令行就足以完成你的模块打包和自动化构建的所有需求。
    在npm script里定义的任务会优先使用本项目下的webpack
  */